package reesoft.idgenerator.service.idstore;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import reesoft.common.code.ErrorCode;
import reesoft.common.util.ApplicationContextUtil;
import reesoft.common.util.StringUtil;
import reesoft.idgenerator.IdGenerateException;

/**
 * 以 Redis 缓存为媒介存储序号
 *
 * Created by reesoft on 2017-08-28
 */
public class RedisIdStore extends BaseIdStore
{
    private static final Logger LOG = LoggerFactory.getLogger(RedisIdStore.class);

    /**
     * Redis 操作对象
     */
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 在 Redis 中存储序号的键的前缀
     */
    private String keyPrefix;

    /**
     * 构造函数
     */
    public RedisIdStore()
    {
        RedisIdStoreConfig config = ApplicationContextUtil.getBean(RedisIdStoreConfig.class);

        if (config != null)
        {
            setRedisTemplate(config.createRedisTemplate());
            setKeyPrefix(config.getKeyPrefix());
        }
    }

    /**
     * 设置 Redis 操作对象
     */
    public void setRedisTemplate(RedisTemplate<String, String> redisTemplate)
    {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 设置在 Redis 中存储序号的键的前缀
     */
    public void setKeyPrefix(String keyPrefix)
    {
        this.keyPrefix = keyPrefix;

        if (this.keyPrefix == null)
        {
            this.keyPrefix = "";
        }
    }

    @Override
    public void init(String name, long initValue, long maxValue) throws IdGenerateException
    {
        LOG.debug("RedisIdStore.init, name:{}, initValue:{}, maxValue:{}", name, initValue, maxValue);

        if (redisTemplate == null)
        {
            throw new IdGenerateException("Redis connection is not ready");
        }

        if (StringUtil.isBlank(name))
        {
            throw new IdGenerateException(ErrorCode.INVALID_PARAMETER);
        }

        setMaxValue(maxValue);

        try
        {
            String key = keyPrefix + name;
            redisTemplate.opsForValue().setIfAbsent(key, String.valueOf(initValue));

            // 校验缓存中存储的值是否为有效的序号，如果无效将抛出异常
            if (redisTemplate.opsForValue().increment(key, 0) == initValue)
            {
                LOG.info("RedisIdStore.init, create ID store with name:{}, initValue:{}", name, initValue);
            }
            else
            {
                // 值已存在，且不是初始值，忽略
            }
        }
        catch (Exception e)
        {
            LOG.error(e.toString());
            throw new IdGenerateException("Failed to set value in redis");
        }
    }

    @Override
    public boolean check(String name)
    {
        //LOG.debug("RedisIdStore.check, name:" + name);

        if (redisTemplate == null)
        {
            return false;
        }

        if (StringUtil.isBlank(name))
        {
            return false;
        }

        try
        {
            String key = keyPrefix + name;

            // 校验缓存中存储的值是否为有效的序号，如果无效将抛出异常
            String existingValue = redisTemplate.opsForValue().get(key);
            if (existingValue != null)
            {
                Long.parseLong(existingValue);
                return true;
            }
        }
        catch (Exception e)
        {
            LOG.error(e.toString());
        }

        return false;
    }

    @Override
    public long loadAndIncrease(String name, long amount) throws IdGenerateException
    {
        LOG.info("RedisIdStore.loadAndIncrease, name:{}, amount:{}", name, amount);

        if (redisTemplate == null)
        {
            throw new IdGenerateException("Redis connection is not ready");
        }

        if (StringUtil.isBlank(name)
                || amount <= 0)
        {
            throw new IdGenerateException(ErrorCode.INVALID_PARAMETER);
        }

        try
        {
            long newValue = redisTemplate.opsForValue().increment(keyPrefix + name, amount);

            if (Long.compareUnsigned(newValue, getMaxValue()) == 1)
            {
                throw new IdGenerateException("Exceed max value");
            }

            long currentValue = newValue - amount;

            LOG.info("RedisIdStore.loadAndIncrease, ID assigned with name:{}, currentValue:{}, amount:{}",
                    name, currentValue, amount);

            return currentValue;
        }
        catch (Exception e)
        {
            LOG.error(e.toString());
            throw new IdGenerateException("Failed to set value in redis");
        }
    }
}
